/*
 * ﻿Copyright (C) 2012-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.ant.types;

import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.Commandline.Argument;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.newmainsoftech.ant.script.ReadMultiLineArgs;

/**
 * Extension of {@link Argument} to support {@link #addText(String)} method what will take 
 * multiple-line arguments being read from text content of corresponding XML tag element. <br />
 * Each argument is extracted as delimited by white space except the case that argument value 
 * containing space(s) is surrounded by double quotation characters.<br />
 * It also resolves ${property_name_or_reference_id} token in each line in multiple-line arguments  
 * to actual value. For the case of the argument containing expression to be resolved for either 
 * property or reference, please be advised that such expression will be resolved to actual string 
 * value even the expression is inside of surrounding quotation character. 
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
public class ArgumentExt extends Argument implements ArgumentExtContract, InvocationHandler {
	Logger logger = LoggerFactory.getLogger( this.getClass());
		protected Logger getLogger() {
			return logger;
		}
		
	public ArgumentExt() {
		super();
		
		// Check contract on Argument class -------------------------------------------------------
			Method[] superMethodArray = Argument.class.getDeclaredMethods();
			Method[] knownArgumentMethods = ArgumentContract.class.getDeclaredMethods();
				if ( superMethodArray.length != knownArgumentMethods.length) {
					throw new ClassCastException(
							String.format(
									"The signature of %1$s is different from known one. " +
									"Please report this incident.", 
									Argument.class.toString()
									)
							);
				}
			for( Method superMethod : superMethodArray) {
				Method method = null;
				try {
					method 
					= ArgumentContract.class.getDeclaredMethod( 
							superMethod.getName(), superMethod.getParameterTypes());
				}
				catch( Exception exception) {
					throw new ClassCastException(
							String.format(
									"The signature of %1$s is different from known one. " +
									"Please report this incident.", 
									Argument.class.toString()
									)
							);
				}
				if ( !method.getReturnType().equals( superMethod.getReturnType())) {
					throw new ClassCastException(
							String.format(
									"The signature of %1$s is different from known one. " +
									"Please report this incident.", 
									Argument.class.toString()
									)
							);
				}
			}
		// ----------------------------------------------------------------------------------------
	}
	
	/* No multi-thread support: not implemented synchronization concept over argList member field 
	 * because super Argument class does not have synchronization concept over parts String[] member 
	 * field implemented. 
	 */
	private List<String> argList = new ArrayList<String>();
		protected List<String> getArgList() {
			if ( argList == null) {
				return new ArrayList<String>();
			}
			ArrayList<String> argListCopy = new ArrayList<String>( argList);
			return argListCopy;
		}
		protected void setArgList( final List<String> argList) {
			if ( argList == null) {
				this.argList = new ArrayList<String>();
			}
			else {
				this.argList = new ArrayList<String>( argList);
			}
		}
	
	private String regExp = null;
		/**
		 * Set regular express string being used to extract each actual argument 
		 * from multiple-line arguments.
		 * @param regExp regular express string being used to extract each actual argument
		 */
		public void setRegExp( String regExp) {
			this.regExp = regExp;
		}
		/**
		 * Get regular express string being used to extract each actual argument 
		 * from multiple-line arguments.
		 * @return regular express string being used to extract each actual argument
		 */
		public String getRegExp() {
			return regExp;
		}

	/**
	 * Extract multi-line arguments from <code>text</code> input and set the extracted arguments to 
	 * {@link #argList} member field via {@link #setArgList(List)} method. <br />
	 * By calling {@link ReadMultiLineArgs#splitByPrefix(String)} method, each argument is extracted as 
	 * delimited by white space except the case that argument value containing space(s) is surrounded by 
	 * double quotation characters.<br />
	 * It also resolves ${property_name_or_reference_id} token in each line in multiple-line arguments  
	 * to actual value. For the case of the argument containing expression to be resolved for either 
	 * property or reference, please be advised that such expression will be resolved to actual string 
	 * value even the expression is inside of surrounding quotation character. 
	 * 
	 * @param text contain multi-line arguments
	 * @see ReadMultiLineArgs#splitByPrefix(String)
	 */
	public void addText( final String text) {
		if ( text == null) return;
		if ( text.trim().length() < 1) return;
		
		ReadMultiLineArgs readMultiLineArgs = new ReadMultiLineArgs();
			String regExp = getRegExp();
				if ( regExp != null) {
					readMultiLineArgs.setRegExpStr( regExp);
				}
				
		List<String> argList = readMultiLineArgs.splitByPrefix( text, getProject());
		setArgList( argList);
	}
	
	@Override
	public String[] getParts() {
		List<String> argListObj = getArgList();
		if ( argListObj.size() < 1) {
			return super.getParts();
		}
		return argListObj.toArray( new String[]{});
	}
	
	public Object invoke( Object proxy, Method method, Object[] args) throws Throwable {
		if ( 
				"getParts".equals( method.getName()) 
				&& ((args == null) ? true : ((args.length < 1) ? true : false))
				) 
		{
			return this.getParts();
		}
		else {
			method.setAccessible( true);
			Object returningObj = method.invoke( this, args);
			
			String methodStr = method.getName();
			Class<?>[] argTypeArray = method.getParameterTypes();
			for( MethodSetPartsField methodSetPartsField : MethodSetPartsField.values()) {
				if ( !methodStr.equals( methodSetPartsField.getArgumentContractMethod().getName())) continue; // for
				if ( !Arrays.equals( argTypeArray, methodSetPartsField.getArgumentContractMethod().getParameterTypes())) 
					continue; // for
				setArgList( null); // clear argList member field
				break; // for
			} // for
			
			return returningObj;
		}
	}
	
	// Dummy override methods of Argument super class (and ArgumentContract interface) ------------
	// These dummy override methods will be advised by ArgumentExtAspect aspect
	@Override
	public void setValue(String value) {
		// TODO Auto-generated method stub
		super.setValue(value);
	}

	@Override
	public void setLine(String line) {
		// TODO Auto-generated method stub
		super.setLine(line);
	}

	@Override
	public void setPath(Path value) {
		// TODO Auto-generated method stub
		super.setPath(value);
	}

	@Override
	public void setPathref(Reference value) {
		// TODO Auto-generated method stub
		super.setPathref(value);
	}

	@Override
	public void setFile(File value) {
		// TODO Auto-generated method stub
		super.setFile(value);
	}

	@Override
	public void setPrefix(String prefix) {
		// TODO Auto-generated method stub
		super.setPrefix(prefix);
	}

	@Override
	public void setSuffix(String suffix) {
		// TODO Auto-generated method stub
		super.setSuffix(suffix);
	}
	// --------------------------------------------------------------------------------------------
}